/**
 * Baco OPC - Baco Sistemas corp.
 */

package bacoopc;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Projeto para o programa de controle. TODO documentar melhor.
 *
 * @author Baco Sistemas
 */
public class Projeto {

    /** Identificador do projeto */
    private int id ;

    /** Nome do projeto */
    private String nome ;

    /** Autor do projeto */
    private String autor ;

    /** Data da criação de um projeto */
    private final Date dataDeCriação ;

    /** Servidores OPC cadastrados a um projeto */
    private final List< ConexaoOPC > servidoresOpc = new ArrayList<ConexaoOPC>();

    /** Algoritmos de controle cadastrados a um projeto */
    private final Map< ConexaoOPC, List<AlgoritmoDeControle> > algoritmos =
            new TreeMap<ConexaoOPC, List<AlgoritmoDeControle> >();

    /** Taxa de atualização do projeto quando ele está executando, em ms. */
    private int taxaDeAtualização ;

    private final AtomicBoolean estáExecutando = new AtomicBoolean(false);

    private ThreadDeProjeto thread;

    /**
     * Cria um novo projeto com data de criação igual à data atual.
     *
     * @param nomeProjeto
     * @param dataCriacaoProjeto
     * @param taxaDeAtualização
     */
    public Projeto( String nomeProjeto , String autor, int taxaDeAtualização ) {
        this( nomeProjeto, autor, taxaDeAtualização, new Date() );
    }

    /**
     * Cria um "novo" projeto dada uma data de criaçao prévia. Utilizados quando
     * se está recuperando um projeto já existente.
     *
     * @param nomeProjeto
     * @param autor
     * @param taxaDeAtualização
     * @param dataDeCriação
     */
    public Projeto( String nomeProjeto , String autor, int taxaDeAtualização,
            Date dataDeCriação ) {
        this.nome = nomeProjeto;
        this.autor = autor;
        this.taxaDeAtualização = taxaDeAtualização;
        this.dataDeCriação = dataDeCriação;
    }

    /**
     * Essa classe serve como thread que executa um projeto. O próprio
     * projeto não pode ser uma thread porque objetos thread em Java só
     * podem executar uma vez na vida deles.
     */
    private static class ThreadDeProjeto extends Thread {

        private Projeto projeto;

        public ThreadDeProjeto( Projeto projeto ) {
            super("Thread - " + projeto.getNome());
            this.projeto = projeto;
        }

        @Override
        public void run(){
            while (true) {

                // checa se deve continuar
                synchronized ( projeto.estáExecutando ) {
                    if ( projeto.estáExecutando.get() == false )
                        return;
                }

                // dá aquela pausa básica na thread
                try {
                    sleep( projeto.taxaDeAtualização );
                } catch (InterruptedException e) {
                    // TODO
                    e.printStackTrace();
                    return;
                }

                // ler valores do servidor OPC
                // rodar algoritmos de controle
                // jogar valores calculados de volta para o servidor OPC
                for ( Map.Entry<ConexaoOPC, List<AlgoritmoDeControle> > pair : projeto.algoritmos.entrySet() ) {
                    ConexaoOPC con = pair.getKey();
                    for ( AlgoritmoDeControle alg : pair.getValue() ) {
                        double v = con.getValor(alg.getVariávelDeEntrada());
                        v = v / 4092.0 * 100;
                        double saida = alg.f(v);
                        saida = (saida / 100) * 4092;
                        con.setVariavel(alg.getVariávelDeSaída(), saida);
                    }
                }
                
                // TODO salvar o log no banco

                // TODO retirar isso
                System.out.println("Projeto - " + projeto.nome + " rodando ");
            }
        }
    }

    /**
     * Executa o projeto. TODO adicionar mais detalhes
     */
    public void executar() {
        estáExecutando.set(true);
        thread = new ThreadDeProjeto(this);
        // conecta aos servidores OPC
        for ( ConexaoOPC con : servidoresOpc )
            con.conectar();
        thread.start();
        for ( ConexaoOPC con : servidoresOpc )
            con.desconectar();
    }

    /**
     * Termina a execução do projeto. TODO adicionar mais detalhes
     */
    public void pararExecução() {
        synchronized ( estáExecutando ) {
            estáExecutando.set(false);
        }
        thread = null;
    }

    public Date getDataCriacao() {
        return dataDeCriação;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getAutor() {
        return this.autor ;
    }

    public void adicionarAlgoritmo( ConexaoOPC conexão, AlgoritmoDeControle algoritmoDeControle ) {
        if ( ! algoritmos.containsKey(conexão) ) {
            algoritmos.put(conexão, new ArrayList<AlgoritmoDeControle>());
            servidoresOpc.add(conexão);
        }
        algoritmos.get(conexão).add(algoritmoDeControle);
    }

    public List<AlgoritmoDeControle> getAlgoritmos( ConexaoOPC conexão ) {
        return algoritmos.get( conexão );
    }

    public Boolean getEstaExecutando() {
        return estáExecutando.get();
    }

    public void setEstáExecutando(Boolean valor) {
        this.estáExecutando.set( valor );
    }

    public List<ConexaoOPC> getServidoresOpc() {
        return servidoresOpc;
    }

    public int getTaxaDeAtualização() {
        return taxaDeAtualização;
    }

    public void setTaxaDeAtualização(int taxaDeAtualização) {
        this.taxaDeAtualização = taxaDeAtualização;
    }

}
